---
title: Semaphore
---
Semaphore,现在普遍翻译为 “信号量”，以前也被翻译成“信号灯”，类似于现实世界中的红绿灯，车辆能不能通行，要看是不是绿等。同样，在编程世界，线程能不能执行，也要看信号量是否允许。

## 信号量模型
信号量模型可以简单概括为 ： **一个计数器，一个等待队列**。在信号量模型里，计数器和等待队列对外是透明的，所以只能通过模型提供的三个方法来访问，这三个方法分别是: init()、down() 和 up().



![6dfeeb9180ff3e038478f2a7dccc9b5c](https://img.wkq.pub/pic/6dfeeb9180ff3e038478f2a7dccc9b5c.webp)



1. init(): 设置计数器的初始值。
2. down()： 计数器的值减一；如果此时计数器的值小于 0 ，则当前线程将被阻塞，否则当前线程可以继续执行。
3. up(): 计数器的值加 1;如果此时计数器的值小于或等于0，则唤醒等待队列中的一个线程，并将其从等待队列中移除。

init() 、down() 和 up() 三个方法都是原子性的，并且这个原子性是由信号量模型实现方保证的。在 Java SDK 里面，信号量模型是由 java.util.concurrent.Semaphore 实现的。

```java title='信号量模型的代码模拟实现'

public class Semaphore {
    //计数器
   private int count;
    //等待队列
    Queue queue;
    //初始化操作
    Semaphore(int count) {
        this.count = count;
    }
    //计数器的值减一
    void down(){
        this.count--;
        if (this.count < 0) {
            //将当前线程插入等待队列
            //阻塞当前线程
        }
    }
    void up() {
        this.count++;
        if (this.count <= 0) {
            //唤醒等待队列中的一个线程
            //唤醒线程 T
        }
    }
}
```

在 Java SDK 并发包里， down() 和 up() 对应的则是 acquire() 和 release()。

## 如何使用信号量

在进入临界区之前执行一下 down() 操作，退出临界区之前执行一下 up 操作就可以了。

```java title='下面是 Java 代码的实现：'
static int count;
//初始化信号量
static fianl Semaphore s = new Semaphore(1);
//用信号量保证互斥
static void addOne(){
    s.acquire();
    try{
     	count+=1;
    }final{
        s.release();
    }
}
```

假设两个线程 T1 和 T2 同时访问 addOne() 方法，当它们同时调用 acquire() 的时候，由于 acquire() 是一个原子操作，所以只能有一个线程（假设 T1）把信号量里计数器的值减为0 ，另一个线程 (T2) 则是将计数器减为 -1。对于线程 T1，信号量里计数器的值是 0，所以继续执行；对于线程 T2，信号量里计数器的值是 -1 所以 T2 被阻塞。



当线程 T1 执行 release() 操作，也就是 up() 操作的时候，信号量里计数器的值是 -1，加 1 之后的值是 0 ，此时等待队列中的线程 2 会被唤醒。于是 T2 在 T1 执行完临界区代码之后才获得了进入临界区的机会，保证了互斥性。

## 实现一个限流器

Java SDK 提供了 Lock ，为什么还要提供一个 Semaphore？

其实实现一个互斥锁，仅仅是 Semaphore 的部分功能，Semaphore 还有一个功能是 Lock 所不具备的，那就是 **Semaphore 可以允许多个线程访问一个临界区。**

比较常见的需求就是工作中遇到的各种池化资源，比如连接池、对象池、线程池。

:::tip 什么是对象池

一次性创建出 N 个对象，之后所有的线程重复利用 N 这个对象，当然对象是被释放前的，也是不允许其它线程使用的。可以使用 List 保存实例对象，但关键的是限流器的设计。

:::

下面是一个对象池的限流示例代码。

```java

class ObjPool<T, R> {
  final List<T> pool;
  // 用信号量实现限流器
  final Semaphore sem;
  // 构造函数
  ObjPool(int size, T t){
    pool = new Vector<T>(){};
    for(int i=0; i<size; i++){
      pool.add(t);
    }
    sem = new Semaphore(size);
  }
  // 利用对象池的对象，调用func
  R exec(Function<T,R> func) {
    T t = null;
    sem.acquire();
    try {
      t = pool.remove(0);
      return func.apply(t);
    } finally {
      pool.add(t);
      sem.release();
    }
  }
}
// 创建对象池
ObjPool<Long, String> pool =
  new ObjPool<Long, String>(10, 2);
// 通过对象池获取t，之后执行
pool.exec(t -> {
    System.out.println(t);
    return t.toString();
});
```
